package com.atguigu.gulimall.search.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.to.elastic.SkuElasticModel;
import com.atguigu.common.utils.R;
import com.atguigu.common.vo.SearchReq;
import com.atguigu.common.vo.SearchResp;
import com.atguigu.gulimall.search.config.ElasticConfig;
import com.atguigu.gulimall.search.constant.ElasticConstant;
import com.atguigu.gulimall.search.feign.ProductFeignService;
import com.atguigu.gulimall.search.service.GulimallSearchService;
import com.atguigu.gulimall.search.vo.AttrVoResp;
import com.atguigu.gulimall.search.vo.BrandVo;
import com.atguigu.gulimall.search.vo.CatalogVo;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.search.TotalHits;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.NestedQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @Author LiDeJie
 * @Create 2024/1/24 20:09
 * @Version 1.0
 */
@Slf4j
@Service
public class GulimallSearchServiceImpl implements GulimallSearchService {
    
    @Autowired
    RestHighLevelClient restHighLevelClient;
    
    @Autowired
    ProductFeignService productFeignService;
    
    @Override
    public SearchResp search(SearchReq searchReq) {
        SearchRequest searchRequest = searchReq(searchReq);
        SearchResponse response = null;
        try {
            response = restHighLevelClient.search(searchRequest, ElasticConfig.COMMON_OPTIONS);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return builderSearchResponse(response, searchReq);
    }
    
    /**
     * 构建响应结果
     */
    private SearchResp builderSearchResponse(SearchResponse response, SearchReq searchReq) {
        SearchResp searchResp = new SearchResp();
        
        //封装sku信息
        if (response.getHits().getHits() != null && response.getHits().getHits().length > 0) {
            List<SkuElasticModel> elasticModelList = Arrays.stream(response.getHits().getHits()).map(skuInfo -> {
                String sourceAsString = skuInfo.getSourceAsString();
                SkuElasticModel skuElasticModel = JSONObject.parseObject(sourceAsString, SkuElasticModel.class);
                if(StringUtils.isNotEmpty(searchReq.getKeyword())) {
                    Text[] skuTitles = skuInfo.getHighlightFields().get("skuTitle").getFragments();
                    skuElasticModel.setSkuTitle(skuTitles[0].string());
                }
                return skuElasticModel;
            }).collect(Collectors.toList());
            searchResp.setProducts(elasticModelList);
           
        }
        
        //处理分页信息
        TotalHits totalHits = response.getHits().getTotalHits();
        searchResp.setPageNum(searchReq.getPageNum());
        searchResp.setTotal(totalHits.value);
        int page = totalHits.value % ElasticConstant.PRODUCT_SIZE == 0 ? (int) (totalHits.value / ElasticConstant.PRODUCT_SIZE) : (int) (totalHits.value / ElasticConstant.PRODUCT_SIZE) + 1;
        searchResp.setTotalPages(page);
        List<Integer> pageNavs = new ArrayList<>();
        for (int i = 1; i <= page; i++) {
            pageNavs.add(i);
        }
        searchResp.setPageNavs(pageNavs);
        
        //构建分类结果
        ParsedLongTerms catalogAgg = response.getAggregations().get("catalogAgg");
        if (CollectionUtils.isNotEmpty(catalogAgg.getBuckets())) {
            List<SearchResp.catalogVo> catalogVoList = catalogAgg.getBuckets().stream().map(catalog -> {
                long catalogId = catalog.getKeyAsNumber().longValue();
                Terms.Bucket catalogNameAgg = ((ParsedStringTerms) catalog.getAggregations().get("catalogNameAgg")).getBuckets().get(0);
                String catalogName = catalogNameAgg.getKeyAsString();
                SearchResp.catalogVo catalogVo = new SearchResp.catalogVo();
                catalogVo.setCatalogId(catalogId);
                catalogVo.setCatalogName(catalogName);
                return catalogVo;
            }).collect(Collectors.toList());
            searchResp.setCatalogVos(catalogVoList);
        }
    
        //构建品牌聚合信息
        ParsedLongTerms brandAgg = response.getAggregations().get("brandAgg");
        if (CollectionUtils.isNotEmpty(brandAgg.getBuckets())) {
            List<SearchResp.BrandVo> BrandVoList = brandAgg.getBuckets().stream().map(brand -> {
                long brandId = brand.getKeyAsNumber().longValue();
                String brandName = ((ParsedStringTerms) brand.getAggregations().get("brandNameAgg")).getBuckets().get(0).getKeyAsString();
                String brandImg = ((ParsedStringTerms) brand.getAggregations().get("brandImgAgg")).getBuckets().get(0).getKeyAsString();
                SearchResp.BrandVo BrandVo = new SearchResp.BrandVo();
                BrandVo.setBrandId(brandId);
                BrandVo.setBrandName(brandName);
                BrandVo.setBrandImg(brandImg);
                return BrandVo;
            }).collect(Collectors.toList());
            searchResp.setBrandVos(BrandVoList);
        }
    
        //构建属性聚合信息
        ParsedNested attrAgg = response.getAggregations().get("attrAgg");
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attrIdAgg");
        if (CollectionUtils.isNotEmpty(attrIdAgg.getBuckets())) {
            List<SearchResp.Attrs> attrsList = attrIdAgg.getBuckets().stream().map(attr -> {
                long attrId = attr.getKeyAsNumber().longValue();
                String attrName = ((ParsedStringTerms) attr.getAggregations().get("attrNameAgg")).getBuckets().get(0).getKeyAsString();
                List<String> attrValues = ((ParsedStringTerms) attr.getAggregations().get("attrValueAgg")).getBuckets().stream().map(attrValue -> attrValue.getKeyAsString()).collect(Collectors.toList());
                SearchResp.Attrs attrs = new SearchResp.Attrs();
                attrs.setAttrId(attrId);
                attrs.setAttrName(attrName);
                attrs.setAttrValue(attrValues);
                return attrs;
            }).collect(Collectors.toList());
            searchResp.setAttrs(attrsList);
        }
        
        //属性面包屑
        if (CollectionUtils.isNotEmpty(searchReq.getAttrs())) {
            List<SearchResp.NavVo> navVoList = searchReq.getAttrs().stream().map(reqAttr -> {
                SearchResp.NavVo navVo = new SearchResp.NavVo();
                String[] attrs = reqAttr.split("_");
                if (attrs != null && attrs.length > 0) {
                    String attrId = attrs[0];
                    R attrResult = null;
                    try {
                        attrResult = productFeignService.info(Long.valueOf(attrId));
                    } catch (NumberFormatException e) {
                        e.printStackTrace();
                        log.error("productFeignService.info field, req: {}, resp: {}, error", attrId, JSONObject.toJSONString(attrResult), e.getMessage());
                    }
                    if (attrResult != null) {
                        AttrVoResp attrVoResp = attrResult.getData("attr", new TypeReference<AttrVoResp>() {});
                        navVo.setNavName(attrVoResp.getAttrName());
                    }else {
                        navVo.setNavName(attrs[0]);
                    }
                    navVo.setNavValue(attrs[1]);
                    String queryString = getQueryString(searchReq, reqAttr, "&attrs=");
                    navVo.setLink("http://search.gmall.com/list.html?" + queryString);
                }
                return navVo;
            }).collect(Collectors.toList());
            searchResp.setNavs(navVoList);
        }
        
        //分类面包屑
        if (ObjectUtils.isNotEmpty(searchReq.getCatalogId())) {
            List<SearchResp.NavVo> navs = searchResp.getNavs();
            SearchResp.NavVo navVo = new SearchResp.NavVo();
            R catalogResult = null;
            try {
                catalogResult = productFeignService.catalogInfo(searchReq.getCatalogId());
            } catch (Exception e) {
                e.printStackTrace();
                log.error("productFeignService.catalogInfo field, req: {}, resp: {}", searchReq.getCatalogId(), JSONObject.toJSONString(catalogResult));
            }
            if (catalogResult != null && catalogResult.getCode() == 0) {
                CatalogVo catalogVo = catalogResult.getData(new TypeReference<CatalogVo>(){});
                navVo.setNavValue(catalogVo.getName());
            }
            navVo.setNavName(String.valueOf(searchReq.getCatalogId()));
    
            String queryString = getQueryString(searchReq, String.valueOf(searchReq.getCatalogId()), "&catalogId=");
            navVo.setLink("http://search.gmall.com/list.html?" + queryString);
            navs.add(navVo);
        }
        
        //品牌面包屑
        if (CollectionUtils.isNotEmpty(searchReq.getBrandId())) {
            //戴尔（DELL）、ThinkPad、联想
            R brandResult = productFeignService.getBrandIds(searchReq.getBrandId());
            if (brandResult != null && brandResult.getCode() == 0) {
                List<BrandVo> brands = brandResult.getData("brands", new TypeReference<List<BrandVo>>() {});
                if (CollectionUtils.isNotEmpty(brands)) {
                    SearchResp.NavVo navVo = new SearchResp.NavVo();
                    StringBuilder builder = new StringBuilder();
                    for (int i = 0; i < brands.size(); i++) {
                        BrandVo brandVo = brands.get(i);
                        if (i == (brands.size() - 1)) {
                            builder.append(brandVo.getName());
                        }else {
                            builder.append(brandVo.getName() + "、");
                        }
                        navVo.setLink("http://search.gmall.com/list.html?" + getQueryString(searchReq, String.valueOf(brandVo.getBrandId()), "&brandId="));
                    }
                    navVo.setNavName("品牌");
                    navVo.setNavValue(builder.toString());
                    searchResp.getNavs().add(navVo);
                }
            }
        }
    
        return searchResp;
    }
    
    /**
     * 获取面包屑URL地址
     */
    private static String getQueryString(SearchReq searchReq, String reqAttr, String value) {
        String encode = null;
        try {
            encode = URLEncoder.encode(reqAttr, "UTF-8");
            encode.replace("%20", "");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            log.error("属性URLEncoder异常：{}", reqAttr);
        }
        String queryString = searchReq.get_queryString().replaceAll(value + encode, "");
        return queryString;
    }
    
    private static SearchRequest searchReq(SearchReq searchReq) {
        SearchRequest searchRequest = new SearchRequest();
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //根据sku标题进行检索
        if (StringUtils.isNotEmpty(searchReq.getKeyword())) {
            boolQuery.must(QueryBuilders.matchQuery("skuTitle", searchReq.getKeyword()));
        }
    
        //根据分类检索
        if (ObjectUtils.isNotNull(searchReq.getCatalogId())) {
            boolQuery.filter(QueryBuilders.termQuery("catalogId", searchReq.getCatalogId()));
        }
        
        //根据品牌Id检索
        if (CollectionUtils.isNotEmpty(searchReq.getBrandId())) {
            searchReq.getBrandId().forEach(brandId -> {
                boolQuery.filter(QueryBuilders.termQuery("brandId", brandId));
            });
        }
        
        //是否有库存
        boolean hasStock = searchReq.getHasStock() == null ? true : searchReq.getHasStock() == 1 ? true : false;
        boolQuery.filter(QueryBuilders.termQuery("hasStock", hasStock));
        
        //价格区间: skuPrice=11_22
        if (StringUtils.isNotEmpty(searchReq.getSkuPrice())) {
            String[] skuPriceRange = searchReq.getSkuPrice().split("_");
            if (skuPriceRange.length == 2) {
                boolQuery.filter(QueryBuilders.rangeQuery("skuPrice").gte(skuPriceRange[0]).lte(skuPriceRange[1]));
            } else {
                String skuPrice = skuPriceRange[0];
                if (searchReq.getSkuPrice().startsWith("_")) {
                    boolQuery.filter(QueryBuilders.rangeQuery("skuPrice").lte(skuPrice));
                } else {
                    boolQuery.filter(QueryBuilders.rangeQuery("skuPrice").gte(skuPrice));
                }
            }
        }
        
        //属性信息: &attrs=3_5.7英寸&attrs=4_193g
        if (CollectionUtils.isNotEmpty(searchReq.getAttrs())) {
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            searchReq.getAttrs().forEach(attr -> {
                String[] attrInfo = attr.split("_");
                if (attrInfo != null && attrInfo.length > 0) {
                    String attrId = attrInfo[0];
                    String attrValue = attrInfo[1];
                    boolQueryBuilder.must(QueryBuilders.termQuery("attrs.attrId", attrId));
                    boolQueryBuilder.must(QueryBuilders.termQuery("attrs.attrValue", attrValue.split(":")));
                    NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", boolQueryBuilder, ScoreMode.None);
                    boolQuery.filter(nestedQuery);
                }
            });
            
        }
        sourceBuilder.query(boolQuery);
        
        //分页
        Integer pageNum = searchReq.getPageNum();
        Integer pageSize = ElasticConstant.PRODUCT_SIZE;
        sourceBuilder.from((pageNum - 1) * pageSize);
        sourceBuilder.size(pageSize);
        
        //排序: sort=saleCount_desc
        if (StringUtils.isNotEmpty(searchReq.getSort())) {
            String[] sort = searchReq.getSort().split("_");
            if (sort != null && sort.length == 2) {
                SortOrder sortOrder = "desc".equals(sort[1]) ? SortOrder.DESC : SortOrder.ASC;
                sourceBuilder.sort(sort[0], sortOrder);
            }
        }
        
        //高亮
        if (StringUtils.isNotEmpty(searchReq.getKeyword())) {
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("skuTitle");
            highlightBuilder.preTags("<b style = 'color: red'>");
            highlightBuilder.postTags("</b>");
            sourceBuilder.highlighter(highlightBuilder);
        }
        
        //分类聚合
        TermsAggregationBuilder catalogAgg = AggregationBuilders.terms("catalogAgg").field("catalogId").size(10);
        catalogAgg.subAggregation(AggregationBuilders.terms("catalogNameAgg").field("catalogName").size(1));
        sourceBuilder.aggregation(catalogAgg);
        //品牌聚合
        TermsAggregationBuilder brandAgg = AggregationBuilders.terms("brandAgg").field("brandId").size(20);
        brandAgg.subAggregation(AggregationBuilders.terms("brandNameAgg").field("brandName").size(1));
        brandAgg.subAggregation(AggregationBuilders.terms("brandImgAgg").field("brandImg").size(1));
        sourceBuilder.aggregation(brandAgg);
        //属性聚合
        NestedAggregationBuilder nested = AggregationBuilders.nested("attrAgg", "attrs");
        TermsAggregationBuilder attrIdAgg = AggregationBuilders.terms("attrIdAgg").field("attrs.attrId").size(50);
        attrIdAgg.subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName").size(1));
        attrIdAgg.subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue").size(20));
        nested.subAggregation(attrIdAgg);
        sourceBuilder.aggregation(nested);
        
        searchRequest.source(sourceBuilder);
        log.info("构建DSL查询语句：{} ", sourceBuilder.toString());
        return searchRequest;
    }
}
